﻿using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;

namespace LDSCore
{
    public class LogFileHelper
    {
        #region 私有变量

        /// <summary>
        /// 文件最大字节数，超过则备份并新建
        /// </summary>
        const int MaxFileSize = 1024 * 100;

        static readonly object ObjLock = new object();

        #endregion

        #region 公共变量

        /// <summary>
        /// 错误日志文件名称
        /// </summary>
        public static string LogFolder = GetRootPath();

        #endregion

        private static string GetRootPath()
        {
            //if (null != HttpContext.Current)
            //return HttpContext.Current.Request.PhysicalApplicationPath + "Log";
            //else
            return AppDomain.CurrentDomain.BaseDirectory + "\\Log";
        }

        /// <summary>
        /// 记录错误信息
        /// </summary>
        /// <param name="err">错误信息</param>
        /// <returns>返回结果</returns>
        public static void Record(string err)
        {
            lock (ObjLock)
            {
                if (null != err)
                {
                    try
                    {
                        // 创建日志记录文件夹
                        Directory.CreateDirectory(LogFolder);

                        #region 修正文件夹路径

                        if (!LogFolder.EndsWith("\\"))
                        {
                            LogFolder += "\\";
                        }

                        #endregion

                        // 按日期生成文件名
                        var file = LogFolder + DateTime.Now.ToShortDateString().Replace('/', '-') + ".txt";

                        // 创建文件信息对象
                        var fi = new FileInfo(file);

                        #region 记录异常信息

                        if (fi.Exists)  // 如果文件存在
                        {
                            if (fi.Length > MaxFileSize)  // 如果文件太大
                            {
                                Backup(file, null);  // 创建备份
                                RecordLog(file, err, false); // 创建新文件记录
                            }
                            else
                            {
                                RecordLog(file, err, true);  // 追加记录
                            }
                        }
                        else
                        {
                            RecordLog(file, err, false); // 创建新文件记录
                        }

                        #endregion
                    }
                    catch (Exception)
                    { }
                }
                else
                {
                    throw new NullReferenceException("错误日志记录保存错误,需要保存的异常对象为Null.");
                }
            }
        }

        /// <summary>
        /// 记录异常信息
        /// </summary>
        /// <param name="file">保存文件名</param>
        /// <param name="err">错误信息</param>
        /// <param name="append">是否追加模式</param>
        private static void RecordLog(string file, string err, bool append)
        {
            var stream = new StreamWriter(file, append, Encoding.GetEncoding("GB2312"));

            if (append) // 是否追加模式
            {
                stream.WriteLine();
                stream.WriteLine("-------------------------------------------------------------------");
            }

            stream.WriteLine("时间：" + DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToLongTimeString());

            var st = new StackTrace(1, true);
            var sfList = st.GetFrames();

            if (sfList != null && sfList.Length > 0)
            {
                stream.WriteLine("错误堆栈信息：");

                for (int i = sfList.Length - 1; i > 0; --i)
                {
                    if (1 == i)
                    {
                        stream.Write(StackFrameToString(sfList[i]));

                        if (!string.IsNullOrEmpty(sfList[i].GetFileName()))
                        {
                            stream.Write(" 位置" + sfList[i].GetFileName() + ":行号 " + sfList[i].GetFileLineNumber());
                        }
                        stream.WriteLine();
                    }
                    else
                    {
                        stream.WriteLine(StackFrameToString(sfList[i]));
                    }
                }
            }

            stream.WriteLine("错误提示：");
            stream.WriteLine("   " + err);

            stream.Close();
        }

        public static string StackFrameToString(StackFrame sf)
        {
            var sb = new StringBuilder();

            MemberInfo mi = sf.GetMethod();
            sb.Append("\r\n   在 ");

            if (mi.DeclaringType != null && !string.IsNullOrEmpty(mi.DeclaringType.Namespace))
            {
                sb.Append(mi.DeclaringType.Namespace);
                sb.Append(".");
            }
            if (mi.DeclaringType != null) sb.Append(mi.DeclaringType.Name);
            sb.Append(".");
            sb.Append(mi.Name);

            sb.Append("(");
            var intParam = 0;
            var paramArray = sf.GetMethod().GetParameters();
            foreach (var param in paramArray)
            {
                intParam += 1;
                sb.Append(param.ParameterType.Name);
                sb.Append(" ");
                sb.Append(param.Name);
                if (intParam != paramArray.Length)
                    sb.Append(", ");
            }
            //sb.Remove(sb.Length - 1, 1);
            sb.Append(")");
            return sb.ToString();
        }

        /// <summary>
        /// 记录异常信息
        /// </summary>
        /// <param name="err">异常对象</param>
        /// <returns>返回结果</returns>
        public static void Record(Exception err)
        {
            Record(err, null);
        }

        /// <summary>
        /// 记录异常信息
        /// </summary>
        /// <param name="err">异常对象</param>
        /// <param name="desc">备注信息</param>
        /// <returns>返回结果</returns>
        public static void Record(Exception err, string desc)
        {
            lock (ObjLock)
            {
                if (null != err)
                {
                    try
                    {
                        // 创建日志记录文件夹
                        Directory.CreateDirectory(LogFolder);

                        #region 修正文件夹路径

                        if (!LogFolder.EndsWith("\\"))
                        {
                            LogFolder += "\\";
                        }

                        #endregion

                        // 按日期生成文件名
                        var file = LogFolder + DateTime.Now.ToShortDateString().Replace('/', '-') + ".txt";

                        // 创建文件信息对象
                        var fi = new FileInfo(file);

                        #region 记录异常信息

                        if (fi.Exists)  // 如果文件存在
                        {
                            if (fi.Length > MaxFileSize)  // 如果文件太大
                            {
                                Backup(file, null);  // 创建备份
                                RecordLog(file, err, desc, false); // 创建新文件记录
                            }
                            else
                            {
                                RecordLog(file, err, desc, true);  // 追加记录
                            }
                        }
                        else
                        {
                            RecordLog(file, err, desc, false); // 创建新文件记录
                        }

                        #endregion
                    }
                    catch (Exception)
                    { }
                }
                else
                {
                    throw new NullReferenceException("错误日志记录保存错误,需要保存的异常对象为Null.");
                }
            }
        }

        /// <summary>
        /// 记录异常信息
        /// </summary>
        /// <param name="file">保存文件名</param>
        /// <param name="desc"></param>
        /// <param name="append">是否追加模式</param>
        /// <param name="err"></param>
        private static void RecordLog(string file, Exception err, string desc, bool append)
        {
            var stream = new StreamWriter(file, append, Encoding.GetEncoding("GB2312"));

            if (append) // 是否追加模式
            {
                stream.WriteLine();
                stream.WriteLine("-------------------------------------------------------------------");
            }
            stream.WriteLine("时间：" + DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToLongTimeString());
            stream.WriteLine("错误堆栈信息：");
            stream.WriteLine(err.StackTrace);
            stream.WriteLine("错误信息：");
            stream.WriteLine("   " + err.Message);

            RecordInnerLog(err, stream);

            if (!string.IsNullOrEmpty(desc))
            {
                stream.WriteLine("备注信息：");
                stream.WriteLine("   " + desc);
            }

            stream.Close();
        }

        /// <summary>
        /// 记录内部异常信息
        /// </summary>
        /// <param name="err"></param>
        /// <param name="stream"></param>
        private static void RecordInnerLog(Exception err, StreamWriter stream)
        {
            if (null != err.InnerException)
            {
                stream.WriteLine("内部信息：");
                stream.WriteLine("   " + err.InnerException.Message);

                RecordInnerLog(err.InnerException, stream);
            }
        }

        #region 文件备份

        /// <summary>
        /// 备份文件
        /// </summary>
        /// <param name="sourceFilePath">需要备份的文件路径</param>
        /// <param name="targetDirectory">备份到的文件夹(不填则自动备份到当前文件夹)</param>
        /// <returns>操作是否成功</returns>
        public static bool Backup(string sourceFilePath, string targetDirectory)
        {
            if (string.IsNullOrEmpty(sourceFilePath))
            {
                throw new NullReferenceException("未指定需要备份的文件路径！");
            }

            var file = new FileInfo(sourceFilePath);
            if (file.Exists)
            {
                #region 构造备份文件完整路径

                string targetFilePath = ConstructTargetFilePath(string.IsNullOrEmpty(targetDirectory) ? file.DirectoryName : targetDirectory, file);

                #endregion

                File.Copy(sourceFilePath, targetFilePath);
                return true;
            }

            return false;
        }

        /// <summary>
        /// 恢复文件
        /// </summary>
        /// <param name="backupFile">备份文件</param>
        /// <param name="targetFile">要恢复的文件</param>
        /// <param name="backupTargetFile">要恢复文件再次备份的名称,如果为null,则不再备份恢复文件</param>
        /// <returns>操作是否成功</returns>
        public static bool Restore(string backupFile, string targetFile, string backupTargetFile)
        {
            if (!File.Exists(backupFile))
            {
                throw new FileNotFoundException(backupFile + "文件不存在！");
            }

            if (string.IsNullOrEmpty(backupTargetFile))
            {
                if (!File.Exists(targetFile))
                {
                    throw new FileNotFoundException(targetFile + "文件不存在！无法备份此文件！");
                }
                if (backupTargetFile != null) File.Copy(targetFile, backupTargetFile, true);
            }

            File.Delete(targetFile);
            File.Copy(backupFile, targetFile);

            return true;
        }

        /// <summary>
        /// 构造备份文件完整路径
        /// </summary>
        /// <param name="targetDirectory">备份文件夹</param>
        /// <param name="file">原始文件信息</param>
        /// <returns>返回备份文件完整路径</returns>
        private static string ConstructTargetFilePath(string targetDirectory, FileInfo file)
        {
            int n = 1;
            string targetFilePath;

            while (true)
            {
                targetFilePath = targetDirectory + @"\" + file.Name + "(" + n.ToString(CultureInfo.InvariantCulture) + ").bak";
                if (!File.Exists(targetFilePath)) break;
                ++n;
            }
            return targetFilePath;
        }

        #endregion
    }
}
